﻿#region Copyright and License Notice
// Copyright (C)2010-2016 - Rob Levine and other contributors
// http://configgen.codeplex.com
// 
// This file is part of ConfigGen.
// 
// ConfigGen is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// ConfigGen is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License and 
// the GNU Lesser General Public License along with ConfigGen.  
// If not, see <http://www.gnu.org/licenses/>
#endregion

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using ConfigGen.Core.ConfigurationSettings;
using ConfigGen.Core.TemplateLoader;
using ConfigGen.Core.TemplateProcessing;
using Moq;
using NUnit.Framework;

namespace ConfigGen.Core.Tests
{
    [TestFixture]
    public class SingleFileGenerationControllerTests
    {
        [Test]
        public void Ctor_NullTemplateProcessorThrows()
        {
            var mockSingleFileGenerator = new Mock<ISingleFileGenerator>();
            var mockOutputWriter = new Mock<IOutputWriter<FileOutputWriterResults>>();

            var ex = Assert.Throws<ArgumentNullException>(() => new SingleFileGenerationController(null, mockSingleFileGenerator.Object, mockOutputWriter.Object));
            Assert.AreEqual("templateProcessor", ex.ParamName, "Incorrect param name");
        }

        [Test]
        public void Ctor_NullSingleFileGeneratorThrows()
        {
            var mockTemplateProcessor = new Mock<ITemplateProcessor>();
            var mockOutputWriter = new Mock<IOutputWriter<FileOutputWriterResults>>();

            var ex = Assert.Throws<ArgumentNullException>(() => new SingleFileGenerationController(mockTemplateProcessor.Object, null, mockOutputWriter.Object));
            Assert.AreEqual("singleFileGenerator", ex.ParamName, "Incorrect param name");
        }

        [Test]
        public void Ctor_NullOutputWriterThrows()
        {
            var mockTemplateProcessor = new Mock<ITemplateProcessor>();
            var mockSingleFileGenerator = new Mock<ISingleFileGenerator>();

            var ex = Assert.Throws<ArgumentNullException>(() => new SingleFileGenerationController(mockTemplateProcessor.Object, mockSingleFileGenerator.Object, null));
            Assert.AreEqual("outputWriter", ex.ParamName, "Incorrect param name");
        }

        [Test]
        public void GenerateFile_NullTemplateLoaderThrows()
        {
            var mockTemplateProcessor = new Mock<ITemplateProcessor>();
            var mockSingleFileGenerator = new Mock<ISingleFileGenerator>();
            var mockOutputWriter = new Mock<IOutputWriter<FileOutputWriterResults>>();
            var machine = new MachineConfigurationSettings("a", "b", new ConfigurationSetting[0]);
            var preferences = new Preferences();

            var itemUnderTest = new SingleFileGenerationController(mockTemplateProcessor.Object, mockSingleFileGenerator.Object, mockOutputWriter.Object);

            var ex = Assert.Throws<ArgumentNullException>(() => itemUnderTest.GenerateFile(null, machine, preferences));
            Assert.AreEqual("templateLoader", ex.ParamName, "Incorrect ParamName");
        }

        [Test]
        public void GenerateFile_NullCurrentMachineThrows()
        {
            var mockTemplateProcessor = new Mock<ITemplateProcessor>();
            var mockSingleFileGenerator = new Mock<ISingleFileGenerator>();
            var mockOutputWriter = new Mock<IOutputWriter<FileOutputWriterResults>>();
            var mockTemplateLoader = new Mock<ITemplateLoader>();
            var preferences = new Preferences();
            
            var itemUnderTest = new SingleFileGenerationController(mockTemplateProcessor.Object, mockSingleFileGenerator.Object, mockOutputWriter.Object);
            
            var ex = Assert.Throws<ArgumentNullException>(() => itemUnderTest.GenerateFile(mockTemplateLoader.Object, null, preferences));
            Assert.AreEqual("currentMachine", ex.ParamName, "Incorrect ParamName");
        }

        [Test]
        public void GenerateFile_NullPreferencesThrows()
        {
            var mockTemplateProcessor = new Mock<ITemplateProcessor>();
            var mockSingleFileGenerator = new Mock<ISingleFileGenerator>();
            var mockOutputWriter = new Mock<IOutputWriter<FileOutputWriterResults>>();
            var mockTemplateLoader = new Mock<ITemplateLoader>();
            var machine = new MachineConfigurationSettings("a", "b", new ConfigurationSetting[0]);

            var itemUnderTest = new SingleFileGenerationController(mockTemplateProcessor.Object, mockSingleFileGenerator.Object, mockOutputWriter.Object);

            var ex = Assert.Throws<ArgumentNullException>(() => itemUnderTest.GenerateFile(mockTemplateLoader.Object, machine, null));
            Assert.AreEqual("preferences", ex.ParamName, "Incorrect ParamName");
        }

        [Test]
        public void GenerateFile001_NonNullConfigurationSettingsAreAddedToTokenUseCount()
        {
            var mockTemplateProcessor = new Mock<ITemplateProcessor>();
            var mockSingleFileGenerator = new Mock<ISingleFileGenerator>();
            var mockOutputWriter = new Mock<IOutputWriter<FileOutputWriterResults>>();
            mockOutputWriter
                .Setup(w => w.WriteFile(It.IsAny<MachineConfigurationSettings>(), It.IsAny<Stream>(), It.IsAny<Preferences>(), It.IsAny<TemplatePreferences>()))
                .Returns(new FileOutputWriterResults());
            
            var mockTemplateLoader = new Mock<ITemplateLoader>();
            var preferences = new Preferences();
            var machine = new MachineConfigurationSettings(
                machineName: "a",
                configFilePath: "b",
                settings: new ConfigurationSetting[] { new ConfigurationSetting("nonNullToken", "nonNullValue"), new ConfigurationSetting("nullToken", null) });

            var itemUnderTest = new SingleFileGenerationController(mockTemplateProcessor.Object, mockSingleFileGenerator.Object, mockOutputWriter.Object);

            var results = itemUnderTest.GenerateFile(mockTemplateLoader.Object, machine, preferences);
            
            Assert.IsNotNull(results, "results: should not be null");
            Assert.IsNotNull(results.TokenUseCounts.FirstOrDefault(t => t.Key == "nonNullValue"), "nonNullValue: should have been present");
            Assert.IsNotNull(results.TokenUseCounts.FirstOrDefault(t => t.Key == "nullValue"), "nullValue: should not have been present");
        }

         [Test]
        public void GenerateFile002_SingleFileGeneratorMethodsCalledInCorrectOrder()
        {
            var mockTemplateProcessor = new Mock<ITemplateProcessor>();
            var methodsCalled = new List<string>();
            var mockSingleFileGenerator = new Mock<ISingleFileGenerator>();
            mockSingleFileGenerator
                .Setup(g => g.Initialise(It.IsAny<MachineConfigurationSettings>(), It.IsAny<TokenCounts>(), It.IsAny<TemplatePreferences>()))
                .Callback((MachineConfigurationSettings s, TokenCounts c, TemplatePreferences p) => methodsCalled.Add("Initialise"));
            mockSingleFileGenerator
                .Setup(g => g.ProcessTemplate(It.IsAny<Stream>(), It.IsAny<ITemplateProcessor>(), It.IsAny<Stream>()))
                .Callback((Stream s, ITemplateProcessor p, Stream d) => methodsCalled.Add("ProcessTemplate"));
            mockSingleFileGenerator
               .Setup(g => g.ReplaceTokens(It.IsAny<Stream>(), It.IsAny<Stream>()))
               .Callback((Stream s, Stream d) => methodsCalled.Add("ReplaceTokens"));
            
            var mockOutputWriter = new Mock<IOutputWriter<FileOutputWriterResults>>();
            mockOutputWriter
                .Setup(w => w.WriteFile(It.IsAny<MachineConfigurationSettings>(), It.IsAny<Stream>(), It.IsAny<Preferences>(), It.IsAny<TemplatePreferences>()))
                .Returns(new FileOutputWriterResults());

            var mockTemplateLoader = new Mock<ITemplateLoader>();
            var preferences = new Preferences();
            var machine = new MachineConfigurationSettings(
                machineName: "a",
                configFilePath: "b",
                settings: new ConfigurationSetting[0]);

            var itemUnderTest = new SingleFileGenerationController(mockTemplateProcessor.Object, mockSingleFileGenerator.Object, mockOutputWriter.Object);
            itemUnderTest.GenerateFile(mockTemplateLoader.Object, machine, preferences);

           Assert.AreEqual(3, methodsCalled.Count, "Incorrect method call count on single file generator");
           Assert.AreEqual("Initialise", methodsCalled[0], "Incorrect 1st call");
           Assert.AreEqual("ProcessTemplate", methodsCalled[1], "Incorrect 2nd call");
           Assert.AreEqual("ReplaceTokens", methodsCalled[2], "Incorrect 3rd call");
        }

        [Test]
        public void GenerateFile003_InitialiseCalledCorrectly()
        {
            var mockTemplateProcessor = new Mock<ITemplateProcessor>();

            MachineConfigurationSettings machineSettingsPassedToInitialise = null;
            TokenCounts tokenCountsPassedToInitialise = null;
            TemplatePreferences templatePreferencesPassedToInitialise = null;

            var mockSingleFileGenerator = new Mock<ISingleFileGenerator>();
            mockSingleFileGenerator
                .Setup(g => g.Initialise(It.IsAny<MachineConfigurationSettings>(), It.IsAny<TokenCounts>(), It.IsAny<TemplatePreferences>()))
                .Callback((MachineConfigurationSettings s, TokenCounts c, TemplatePreferences p) =>
                              {
                                  machineSettingsPassedToInitialise = s;
                                  tokenCountsPassedToInitialise = c;
                                  templatePreferencesPassedToInitialise = p;
                              });
        
            var mockOutputWriter = new Mock<IOutputWriter<FileOutputWriterResults>>();
            mockOutputWriter
                .Setup(w => w.WriteFile(It.IsAny<MachineConfigurationSettings>(), It.IsAny<Stream>(), It.IsAny<Preferences>(), It.IsAny<TemplatePreferences>()))
                .Returns(new FileOutputWriterResults());

            var templatePreferences = new TemplatePreferences();
            var mockTemplateLoader = new Mock<ITemplateLoader>();
            mockTemplateLoader
                .Setup(l=>l.TemplatePreferences)
                .Returns(templatePreferences);
            var preferences = new Preferences();
            var machine = new MachineConfigurationSettings(
                machineName: "a",
                configFilePath: "b",
                settings: new ConfigurationSetting[0]);

            var itemUnderTest = new SingleFileGenerationController(mockTemplateProcessor.Object, mockSingleFileGenerator.Object, mockOutputWriter.Object);
            itemUnderTest.GenerateFile(mockTemplateLoader.Object, machine, preferences);

            mockSingleFileGenerator
                .Verify(g => g.Initialise(It.IsAny<MachineConfigurationSettings>(), It.IsAny<TokenCounts>(), It.IsAny<TemplatePreferences>()), Times.Once(), "Initialise: not called, or called incorrectly");
            Assert.AreSame(machine, machineSettingsPassedToInitialise, "machineSettingsPassedToInitialise: incorrect");
            Assert.IsNotNull(tokenCountsPassedToInitialise, "tokenCountsPassedToInitialise: incorrect");
            Assert.AreSame(templatePreferences, templatePreferencesPassedToInitialise, "templatePreferencesPassedToInitialise: incorrect");
        }

        [Test]
        public void GenerateFile004_ProcessTemplateCalledCorrectly()
        {
            var mockTemplateProcessor = new Mock<ITemplateProcessor>();
          
            Stream srcStreamPassedToProcessTemplate = null;
            ITemplateProcessor templateProcessorPassedToProcessTemplate = null;
            Stream destStreamPassedToProcessTemplate = null;

            var mockSingleFileGenerator = new Mock<ISingleFileGenerator>();
            mockSingleFileGenerator
                .Setup(g => g.ProcessTemplate(It.IsAny<Stream>(), It.IsAny<ITemplateProcessor>(), It.IsAny<Stream>()))
                .Callback((Stream s, ITemplateProcessor p, Stream d) =>
                              {
                                  srcStreamPassedToProcessTemplate = s;
                                  templateProcessorPassedToProcessTemplate = p;
                                  destStreamPassedToProcessTemplate = d;
                              });

            var mockOutputWriter = new Mock<IOutputWriter<FileOutputWriterResults>>();
            mockOutputWriter
                .Setup(w => w.WriteFile(It.IsAny<MachineConfigurationSettings>(), It.IsAny<Stream>(), It.IsAny<Preferences>(), It.IsAny<TemplatePreferences>()))
                .Returns(new FileOutputWriterResults());

            var templateStream = new MemoryStream();
            var mockTemplateLoader = new Mock<ITemplateLoader>();
            mockTemplateLoader
                .Setup(l=>l.GetTemplateStream())
                .Returns(templateStream);

            var preferences = new Preferences();
            var machine = new MachineConfigurationSettings(
                machineName: "a",
                configFilePath: "b",
                settings: new ConfigurationSetting[0]);

            var itemUnderTest = new SingleFileGenerationController(mockTemplateProcessor.Object, mockSingleFileGenerator.Object, mockOutputWriter.Object);
            itemUnderTest.GenerateFile(mockTemplateLoader.Object, machine, preferences);

            mockSingleFileGenerator
                .Verify(g => g.ProcessTemplate(It.IsAny<Stream>(), It.IsAny<ITemplateProcessor>(), It.IsAny<Stream>()), Times.Once(), "ProcessTemplate: not called, or called incorrectly");

            Assert.AreSame(templateStream, srcStreamPassedToProcessTemplate, "srcStreamPassedToProcessTemplate: incorrect");
            Assert.AreSame(templateProcessorPassedToProcessTemplate, mockTemplateProcessor.Object, "templateProcessorPassedToProcessTemplate: incorrect");
            Assert.IsNotNull(destStreamPassedToProcessTemplate, "destStreamPassedToProcessTemplate: should not be null");
        }


        [Test]
        public void GenerateFile005_ReplaceTokensCalledCorrectly()
        {
            var mockTemplateProcessor = new Mock<ITemplateProcessor>();

            Stream srcStreamPassedToReplaceTokens = null;
            Stream destStreamPassedToReplaceTokens = null;

            var mockSingleFileGenerator = new Mock<ISingleFileGenerator>();
            mockSingleFileGenerator
                .Setup(g => g.ReplaceTokens(It.IsAny<Stream>(), It.IsAny<Stream>()))
                .Callback((Stream s, Stream d) =>
                {
                    srcStreamPassedToReplaceTokens = s;
                    destStreamPassedToReplaceTokens = d;
                });

            var mockOutputWriter = new Mock<IOutputWriter<FileOutputWriterResults>>();
            mockOutputWriter
                .Setup(w => w.WriteFile(It.IsAny<MachineConfigurationSettings>(), It.IsAny<Stream>(), It.IsAny<Preferences>(), It.IsAny<TemplatePreferences>()))
                .Returns(new FileOutputWriterResults());

            var mockTemplateLoader = new Mock<ITemplateLoader>();
      
            var preferences = new Preferences();
            var machine = new MachineConfigurationSettings(
                machineName: "a",
                configFilePath: "b",
                settings: new ConfigurationSetting[0]);

            var itemUnderTest = new SingleFileGenerationController(mockTemplateProcessor.Object, mockSingleFileGenerator.Object, mockOutputWriter.Object);
            itemUnderTest.GenerateFile(mockTemplateLoader.Object, machine, preferences);

            mockSingleFileGenerator
                .Verify(g => g.ReplaceTokens(It.IsAny<Stream>(), It.IsAny<Stream>()), Times.Once(), "ProcessTemplate: not called, or called incorrectly");

            Assert.IsNotNull(srcStreamPassedToReplaceTokens, "srcStreamPassedToReplaceTokens: should not be null");
            Assert.IsNotNull(destStreamPassedToReplaceTokens, "destStreamPassedToReplaceTokens: should not be null");
        }

        [Test]
        public void GenerateFile006_OutputWriterCalledCorrectly()
        {
            var mockTemplateProcessor = new Mock<ITemplateProcessor>();
            var mockSingleFileGenerator = new Mock<ISingleFileGenerator>();

            MachineConfigurationSettings configSettingsPassedToWriteFile = null;
            Stream streamPassedToWriteFile = null;
            Preferences preferencesPassedToWriteFile = null;
            TemplatePreferences templatePreferencesPassedToWriteFile = null;

            var mockOutputWriter = new Mock<IOutputWriter<FileOutputWriterResults>>();
            mockOutputWriter
                .Setup(w => w.WriteFile(It.IsAny<MachineConfigurationSettings>(), It.IsAny<Stream>(), It.IsAny<Preferences>(), It.IsAny<TemplatePreferences>()))
                .Callback((MachineConfigurationSettings mcs, Stream s, Preferences p, TemplatePreferences tp) =>
                              {
                                  configSettingsPassedToWriteFile = mcs;
                                  streamPassedToWriteFile = s;
                                  preferencesPassedToWriteFile = p;
                                  templatePreferencesPassedToWriteFile = tp;
                              })
                .Returns(new FileOutputWriterResults());

            var templatePreferences = new TemplatePreferences();
            var mockTemplateLoader = new Mock<ITemplateLoader>();
            mockTemplateLoader
                .Setup(l => l.TemplatePreferences)
                .Returns(templatePreferences);

            var preferences = new Preferences();
            var machine = new MachineConfigurationSettings(
                machineName: "a",
                configFilePath: "b",
                settings: new ConfigurationSetting[0]);

            var itemUnderTest = new SingleFileGenerationController(mockTemplateProcessor.Object, mockSingleFileGenerator.Object, mockOutputWriter.Object);
            itemUnderTest.GenerateFile(mockTemplateLoader.Object, machine, preferences);

            Assert.AreSame(machine, configSettingsPassedToWriteFile, "configSettingsPassedToWriteFile: incorrect");
            Assert.IsNotNull(streamPassedToWriteFile, "streamPassedToWriteFile: should not have been null");
            Assert.AreSame(preferences, preferencesPassedToWriteFile, "preferencesPassedToWriteFile: incorrect");
            Assert.AreSame(templatePreferences, templatePreferencesPassedToWriteFile, "templatePreferencesPassedToWriteFile: incorrect");
        }


        [Test]
        public void GenerateFile007_SingleFileGenerationResultsConstructedCorrectly()
        {
            var mockTemplateProcessor = new Mock<ITemplateProcessor>();
            var mockSingleFileGenerator = new Mock<ISingleFileGenerator>();

            var fileOutputWriterResults = new FileOutputWriterResults()
                                              {
                                                  GeneratedFile = new FileInfo("someFile"),
                                                  NewMd5 = new byte[] { 0x1 },
                                                  OldMd5 = new byte[] { 0x2 }
                                              };
            var mockOutputWriter = new Mock<IOutputWriter<FileOutputWriterResults>>();
            mockOutputWriter
                .Setup(w => w.WriteFile(It.IsAny<MachineConfigurationSettings>(), It.IsAny<Stream>(), It.IsAny<Preferences>(), It.IsAny<TemplatePreferences>()))
                .Returns(fileOutputWriterResults);

            var templatePreferences = new TemplatePreferences();
            var mockTemplateLoader = new Mock<ITemplateLoader>();
            mockTemplateLoader
                .Setup(l => l.TemplatePreferences)
                .Returns(templatePreferences);

            var preferences = new Preferences();
            var machine = new MachineConfigurationSettings(
                machineName: "testMachineName",
                configFilePath: "b",
                settings: new ConfigurationSetting[0]);

            var itemUnderTest = new SingleFileGenerationController(mockTemplateProcessor.Object, mockSingleFileGenerator.Object, mockOutputWriter.Object);
            var results = itemUnderTest.GenerateFile(mockTemplateLoader.Object, machine, preferences);

            Assert.IsNotNull(results, "results: should not be null");
            Assert.AreEqual("testMachineName", results.MachineName, "MachineName: incorrect");
            Assert.AreSame(fileOutputWriterResults.GeneratedFile, results.GeneratedFile, "GeneratedFile: incorrect");
            Assert.AreSame(fileOutputWriterResults.NewMd5, results.NewFileMd5Hash, "NewFileMd5Hash: incorrect");
            Assert.AreSame(fileOutputWriterResults.OldMd5, results.OldFileMd5Hash, "OldFileMd5Hash: incorrect");
        }
    }
}
